/*
* Copyright (c) 2009, Roc King
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the OwnWaterloo,Roc King nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <assert.h>
#include <string.h>

#include "callback_currying/cdecl_partial_application.h"

#ifndef CALLBACK_CURRYING_NO_PORTABILITY_SIZE_CHECK

static const char g_32bit_check[sizeof(uintptr_t)==sizeof(uint32_t)?1:-1] = {0};
static const char g_size_check
[sizeof(cdecl_partial_application)==CC_CDECL_THUNK_SIZE?1:-1] = {0};

#endif  /* #ifndef CALLBACK_CURRYING_NO_PORTABILITY_SIZE_CHECK */

static const uint8_t g_adjust_machine_code[] = {
    0x58,               /* pop  eax */
    0x59,               /* pop  ecx */
    0x83,0xe9,0x12,     /* sub  ecx 12h */
    0x2b,0xc8,          /* sub  ecx,eax */
    0x89,0x48,0x0e,     /* mov  dword ptr[eax+0eh],ecx */
    0x89,0x60,0x09,     /* mov  dword ptr[eax+9],esp */
    0xff,0x70,0x04,     /* push dword ptr[eax+4] */
    0x8b,0xc8,          /* mov  ecx,eax */
    0x83,0xc1,0x08,     /* add  ecx,8 */
    0x51,               /* push ecx */
    0xff,0x20,          /* jmp  dword ptr[eax] */
};

static const uintptr_t cdecl_adjust_function_ptr
                                   = (uintptr_t)&g_adjust_machine_code;

enum cdecl_partial_application_offset_ {
    offset_to_call              = 0,
    offset_to_adjust            = 1,
    offset_to_applied_function  = 5,
    offset_to_fixed_argument    = 9,
    offset_to_mov_esp           = 10,
    offset_to_jmp_old_return    = 15,
};

void
cdecl_initialize(cdecl_partial_application* papply)
{
    /** the layout of machine code */
    static const cdecl_partial_application machine_code = { {
        0xe8,                   /* call (dword offset) */
        0xcc,0xcc,0xcc,0xcc,    /* offset to adjust function placeholder */
        0xcc,0xcc,0xcc,0xcc,    /* applied function */
        0xcc,0xcc,0xcc,0xcc,    /* fixed argument */
        0xbc,                   /* mov esp imm */
        0xcc,0xcc,0xcc,0xcc,    /* esp stored */
        0xe9,                   /* jmp old return address */
        0xcc,0xcc,0xcc,0xcc,    /* old return address stored */
    } };
    /* 0xcc is the intermit 3 instruction */

    uintptr_t adjust = 0;
    if (!papply)
    {
        assert(0);
        return ;
    }
    memcpy(papply,&machine_code,CC_CDECL_THUNK_SIZE);
    adjust = cdecl_adjust_function_ptr - 5
           - (uintptr_t)&papply->machine_code[offset_to_call];
    memcpy(&papply->machine_code[offset_to_adjust],&adjust,sizeof(adjust));
}

void
cdecl_set_applied_function(cdecl_partial_application* papply
                           ,uintptr_t applied_function)
{
    if (!papply)
    {
        assert(0);
        return ;
    }
    memcpy(&papply->machine_code[offset_to_applied_function]
          ,&applied_function,sizeof(applied_function));
}

uintptr_t
cdecl_get_applied_function(const cdecl_partial_application* papply)
{
    uintptr_t applied = 0;
    if (!papply)
    {
        assert(0);
        return 0;
    }
    memcpy(&applied,&papply->machine_code[offset_to_applied_function]
          ,sizeof(applied));
    return applied;
}

void
cdecl_set_fixed_argument(cdecl_partial_application* papply
                         ,uintptr_t fixed_argument)
{
    if (!papply)
    {
        assert(0);
        return ;
    }
    memcpy(&papply->machine_code[offset_to_fixed_argument],&fixed_argument
          ,sizeof(fixed_argument) );
}

uintptr_t cdecl_get_fixed_argument(const cdecl_partial_application* papply)
{
    uintptr_t argument = 0;
    if (!papply)
    {
        assert(0);
        return 0;
    }
    memcpy(&argument,&papply->machine_code[offset_to_fixed_argument]
          ,sizeof(argument));
    return argument;
}
